package com.eischet.lookup.web;

import org.apache.log4j.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.function.CustomScoreProvider;
import org.apache.lucene.search.function.CustomScoreQuery;

import java.io.IOException;
import java.util.Date;

/**
 * This class extends Lucene to provide search results ordered by recency.
 * This means that, given the exact same score based on the user's query,
 * more recent Documents will be higher up in the list of search results.
 * This does <i>not</i> mean that normal scoring is disabled; this is <i>not</i>
 * intended to sort search results <i>only</i> by date.
 */
class RecencyBoostingQuery extends CustomScoreQuery {
    private static final Logger logger = Logger.getLogger(RecencyBoostingQuery.class);

    private double multiplier;
    private int today;
    private int maxDaysAgo;
    private String dayField;

    /**
     * Create a new recency boosting query from an unwrapped query.
     *
     * @param q             a Query
     * @param multiplier    a boosting multiplier
     * @param maxDaysAgo    number of days to allow
     * @param dayField      day field / timestamp field to use
     */
    public RecencyBoostingQuery(Query q, double multiplier, int maxDaysAgo, String dayField) {
        super(q);
        int MSEC_PER_DAY = 1000 * 3600 * 24;
        today = (int) (new Date().getTime()/ MSEC_PER_DAY);
        this.multiplier = multiplier;
        this.maxDaysAgo = maxDaysAgo;
        this.dayField = dayField;
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("today=%s multiplier=%s maxDaysAgo=%s, dayField='%s'", today, multiplier, maxDaysAgo, dayField));
        }
    }

    /**
     * This class implements the boosting, by being a CustomScoreProvider that increases the
     * scores for recent documents.
     * This is ripped straight out of the Lucene book, 2nd edition. See there for details on
     * how it works.
     */
    private class RecencyBooster extends CustomScoreProvider {
        private final int[] publishDay;

        /**
         * create a new recency booster
         * @param r                 an index reader
         * @throws IOException      on errors
         */
        public RecencyBooster(IndexReader r) throws IOException {
            super(r);
            publishDay = FieldCache.DEFAULT.getInts(r, dayField);
        }

        /**
         * calculate a custom score for a document
         *
         * @param doc               the document
         * @param subQueryScore     see Lucene documentation
         * @param valSrcScore       see Lucene documentation
         * @return                  a recency-boosted score
         */
        public float customScore(int doc, float subQueryScore, float valSrcScore) {
            int daysAgo = today - publishDay[doc];
            if (daysAgo < maxDaysAgo) {
                float boost = (float) (multiplier * (maxDaysAgo-daysAgo) / maxDaysAgo);
                return (float) (subQueryScore * (1.0+boost));
            } else {
                return subQueryScore;
            }
        }

        /**
         * @return a String representation of this object
         */
        @Override
        public String toString() {
            return String.format("RecencyBooster(publishDay=%s)", publishDay);
        }
    }

    /**
     * return a new recency booster instance
     *
     * @param r                 an index reader
     * @return                  a custom score provider
     * @throws IOException      when all else fails
     */
    public CustomScoreProvider getCustomScoreProvider(IndexReader r) throws IOException {
        return new RecencyBooster(r);
    }

    /**
     * @return a String representation of this object
     */
    @Override
    public String toString() {
        return String.format("RecencyBoostingQuery(dayField='%s', multiplier=%s, today=%d, maxDaysAgo=%d)", dayField, multiplier, today, maxDaysAgo);
    }
}
